/*
 * samsung MCU USB dnw adaptor driver
 *
 * Original driver for unknown by anonymous
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License version
 *      2 as published by the Free Software Foundation.
 *
 */
#include "secbulk.h"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

/*
 * Version Information
 */
#define DRIVER_DESC "samsung MCU USB dnw adaptor driver"

static int debug;
static struct usb_driver secbulk_driver;

#define SECBULK_MAJOR       102
#define SECBULK_MINOR       0
#define DRIVER_NAME         "secbulk"


struct secbulk_dev {
    struct usb_device *usbdev;
    struct mutex mutex;
    void *out_buf;
    __u8 out_endpoint;
};


static int secbulk_open(struct inode *node, struct file *file) {
    struct usb_interface *usbif;
    struct secbulk_dev *dev;

    if (!(usbif = usb_find_interface(&secbulk_driver, iminor(node))))
        return -ENODEV;

    dev = usb_get_intfdata(usbif);
    if (!(dev->out_buf = kzalloc(BULKOUT_BUFFER_SIZE, GFP_KERNEL)))
        return -ENOMEM;
    if (!mutex_trylock(&dev->mutex)) {
        kfree(dev->out_buf);
        dev->out_buf = NULL;
        return -EBUSY;
    }
    file->private_data = dev;

    return 0;
}

static int secbulk_release(struct inode *node, struct file *file) {
    struct secbulk_dev *dev = (struct secbulk_dev*) (file->private_data);
    kfree(dev->out_buf);
    dev->out_buf = NULL;
    mutex_unlock(&dev->mutex);

    return 0;
}

static ssize_t secbulk_read(struct file *file,
        char __user *buf, size_t len, loff_t *loff) {
    return -EPERM;
}

static ssize_t secbulk_write(struct file *file,
        const char __user *data, size_t len, loff_t *loff) {
    int ret;
    size_t n;
    int actruln;
    size_t totaln = 0;
    struct secbulk_dev *dev = file->private_data;
    unsigned int pipe = usb_sndbulkpipe(dev->usbdev, dev->out_endpoint);

    while(len) {
        n = min(len, (size_t)BULKOUT_BUFFER_SIZE);

        if(copy_from_user(dev->out_buf, data + totaln, n)) {
            printk(KERN_ERR DRIVER_NAME ": copy_from_user failed!\n");
            return -EFAULT;
        }

        if((ret = usb_bulk_msg(dev->usbdev, pipe,
                dev->out_buf, n, &actruln, 0))) {
            printk(KERN_ERR DRIVER_NAME ": usb_bulk_msg failed!\n");
            return -EFAULT;
        }

        len -= actruln;
        totaln += actruln;
    }

    return totaln;
}

static struct file_operations fops = {
    .owner      = THIS_MODULE,
    .read       = secbulk_read,
    .write      = secbulk_write,
    .open       = secbulk_open,
    .release    = secbulk_release,
};

static struct usb_class_driver usb_class_driver = {
    .name       = DRIVER_NAME "%d",
    .fops       = &fops,
    .minor_base = 100,
};

static int secbulk_probe(struct usb_interface *usbif, const struct usb_device_id *id) {
    int ret;
    struct secbulk_dev *dev;
    struct usb_host_interface *usbhostif;
    struct usb_endpoint_descriptor *endpoint;
    int i;

    printk(KERN_INFO DRIVER_NAME ": " DRIVER_NAME " probing...\n");

    if ((dev = kzalloc(sizeof(*dev), GFP_KERNEL)) == NULL) {
        ret = -ENOMEM;
        goto error;
    }

    usbhostif = usbif->cur_altsetting;
    for (i = 0; i < usbhostif->desc.bNumEndpoints; ++i) {
        endpoint = &usbhostif->endpoint[i].desc;
        if (usb_endpoint_is_bulk_out(endpoint)) {
            printk(KERN_INFO DRIVER_NAME ": bulk out endpoint found!\n");
            dev->out_endpoint = endpoint->bEndpointAddress;
            break;
        }
    }

    if (!(dev->out_endpoint)) {
        ret = -EBUSY;
        goto error;
    }

    if ((ret = usb_register_dev(usbif, &usb_class_driver))) {
        printk(KERN_ERR DRIVER_NAME ": usb_register_dev failed!\n");
        goto error;
    }

    dev->usbdev = usb_get_dev(interface_to_usbdev(usbif));

    usb_set_intfdata(usbif, dev);

    mutex_init(&dev->mutex);

    return 0;

error:
    if (dev)
        kfree(dev);
    return ret;
}

static void secbulk_disconnect(struct usb_interface *usbif) {
    struct secbulk_dev *dev;
    printk(KERN_INFO DRIVER_NAME ": " DRIVER_NAME " disconnected!\n");
    if ((dev = usb_get_intfdata(usbif)))
        kfree(dev);
    usb_deregister_dev(usbif, &usb_class_driver);
}

static struct usb_device_id id_table[] = {
    { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_ID) },
    { } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, id_table);

static struct usb_driver usb_driver = {
	.name                   = DRIVER_NAME,
	.probe                  = secbulk_probe,
	.disconnect             = secbulk_disconnect,
	.id_table               = id_table,
	.supports_autosuspend   = 0,
};

static int __init secbulk_init(void) {
    int result;

    printk(KERN_INFO DRIVER_NAME ": " DRIVER_NAME " loaded\n");

    if ((result = usb_register(&usb_driver))) {
        printk(KERN_ERR DRIVER_NAME ": usb_register failed: %d", result);
        return result;
    }

    return 0;
}

static void __exit secbulk_exit(void) {
    usb_deregister(&usb_driver);
    printk(KERN_INFO DRIVER_NAME ": " DRIVER_NAME " unloaded\n");
}

module_init(secbulk_init);
module_exit(secbulk_exit);

MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");
